﻿//
// (C) Copyright 2009 Irantha Suwandarathna (iranthas@hotmail.com)
// All rights reserved.
//

/* Copyright (c) 2001-2008, The HSQL Development Group
 * All rights reserved.
 *
 * Redistribution and use _in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *
 * Redistributions of source code must retain the above copyright notice, this
 * list of conditions and the following disclaimer.
 *
 * Redistributions _in binary form must reproduce the above copyright notice,
 * this list of conditions and the following disclaimer _in the documentation
 * and/or other materials provided with the distribution.
 *
 * Neither the name of the HSQL Development Group nor the names of its
 * contributors may be used to endorse or promote products derived from this
 * software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED. IN NO EVENT SHALL HSQL DEVELOPMENT GROUP, HSQLDB.ORG,
 * OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using EffiProz.Core.Lib;
using EffiProz.Core.DataTypes;
using EffiProz.Core.Indexes;
using EffiProz.Core.Errors;
using EffiProz.Core.Persist;
using EffiProz.Core.Results;


namespace EffiProz.Core
{
    public class StatementCompound : Statement
    {
        public bool isLoop;
        public QNameManager.QName label;
        public StatementHandler[] handlers = StatementHandler.emptyExceptionHandlerArray;
        public Statement loopCursor;
        public Statement[] statements;
        public StatementExpression condition;
        public bool isAtomic;

        //
        public ColumnSchema[] variables = ColumnSchema.emptyArray;
        public HashMappedList scopeVariables;
        public RangeVariable[] rangeVariables = RangeVariable.emptyArray;

        //
        public static StatementCompound[] emptyStatementArray =
            new StatementCompound[] { };

        public StatementCompound(int type, QNameManager.QName label)
            : base(type, StatementTypes.X_SQL_CONTROL)
        {

            this.label = label;
            _isTransactionStatement = false;

            switch (type)
            {

                case StatementTypes.LOOP:
                case StatementTypes.WHILE:
                case StatementTypes.REPEAT:
                    isLoop = true;
                    break;

                case StatementTypes.BEGIN_END:
                case StatementTypes.IF:
                    isLoop = false;
                    break;

                default:
                    throw Error.runtimeError(ErrorCode.U_S0500,
                                              "StatementCompound");
            }
        }

        public override String getSQL()
        {

            /*
                    StringBuilder sb = new StringBuilder();

                    if (label != null) {
                        sb.Append(label.getStatementName()).Append(':').Append(' ');
                    }

                    switch (type) {

                        case StatementTypes.LOOP :
                            sb.Append(Tokens.T_LOOP).Append(' ');

                            for (int i = 0; i < statements.Length; i++) {
                                sb.Append(statements[i].getSQL()).Append(';');
                            }

                            sb.Append(Tokens.T_END).Append(' ').Append(Tokens.T_LOOP);
                            break;

                        case StatementTypes.WHILE :
                            sb.Append(Tokens.T_WHILE).Append(' ');
                            sb.Append(condition.getSQL()).Append(' ').Append(Tokens.T_DO);
                            sb.Append(' ');

                            for (int i = 0; i < statements.Length; i++) {
                                sb.Append(statements[i].getSQL()).Append(';');
                            }

                            sb.Append(Tokens.T_END).Append(' ').Append(Tokens.T_WHILE);
                            break;

                        case StatementTypes.REPEAT :
                            sb.Append(Tokens.T_REPEAT).Append(' ');

                            for (int i = 0; i < statements.Length; i++) {
                                sb.Append(statements[i].getSQL()).Append(';');
                            }

                            sb.Append(Tokens.T_UNTIL).Append(' ');
                            sb.Append(condition.getSQL()).Append(' ');
                            sb.Append(Tokens.T_END).Append(' ').Append(Tokens.T_REPEAT);
                            break;

                        case StatementTypes.BEGIN_END :
                            sb.Append(Tokens.T_BEGIN).Append(' ').Append(Tokens.T_ATOMIC);
                            sb.Append(' ');

                            for (int i = 0; i < handlers.Length; i++) {
                                sb.Append(handlers[i].getSQL()).Append(';');
                            }

                            for (int i = 0; i < variables.Length; i++) {
                                sb.Append(Tokens.T_DECLARE).Append(' ');
                                sb.Append(variables[i].getSQL());

                                if (variables[i].hasDefault()) {
                                    sb.Append(' ').Append(Tokens.T_DEFAULT).Append(' ');
                                    sb.Append(variables[i].getDefaultDDL());
                                }

                                sb.Append(';');
                            }

                            for (int i = 0; i < statements.Length; i++) {
                                sb.Append(statements[i].getSQL()).Append(';');
                            }

                            sb.Append(Tokens.T_END);
                            break;

                        case StatementTypes.IF :
                            for (int i = 0; i < statements.Length; i++) {
                                if (statements[i].type == StatementTypes.CONDITION) {
                                    if (i != 0) {
                                        sb.Append(Tokens.T_ELSE).Append(' ');
                                    }

                                    sb.Append(Tokens.T_IF).Append(' ');
                                    sb.Append(statements[i].getSQL()).Append(' ');
                                    sb.Append(Tokens.T_THEN).Append(' ');
                                } else {
                                    sb.Append(statements[i].getSQL()).Append(';');
                                }
                            }

                            sb.Append(Tokens.T_END).Append(' ').Append(Tokens.T_IF);
                            break;
                    }

                    return sb.ToString();
            */
            return sql;
        }

        protected String describe(Session session, int blanks)
        {

            StringBuilder sb = new StringBuilder();

            sb.Append('\n');

            for (int i = 0; i < blanks; i++)
            {
                sb.Append(' ');
            }

            sb.Append(Tokens.T_STATEMENT);

            return sb.ToString();
        }

        public void setLocalDeclarations(Object[] declarations)
        {

            int varCount = 0;
            int handlerCount = 0;

            for (int i = 0; i < declarations.Length; i++)
            {
                if (declarations[i] is ColumnSchema)
                {
                    varCount++;
                }
                else
                {
                    handlerCount++;
                }
            }

            variables = new ColumnSchema[varCount];
            handlers = new StatementHandler[handlerCount];
            varCount = 0;
            handlerCount = 0;

            for (int i = 0; i < declarations.Length; i++)
            {
                if (declarations[i] is ColumnSchema)
                {
                    variables[varCount++] = (ColumnSchema)declarations[i];
                }
                else
                {
                    StatementHandler handler = (StatementHandler)declarations[i];

                    handler.setParent(this);

                    handlers[handlerCount++] = handler;
                }
            }

            setVariables();
            setHandlers();
        }

        public void setLoopStatement(Statement cursorStatement)
        {
            loopCursor = cursorStatement;
        }

        public void setStatements(Statement[] statements)
        {

            for (int i = 0; i < statements.Length; i++)
            {
                statements[i].setParent(this);
            }

            this.statements = statements;
        }

        public void setCondition(StatementExpression condition)
        {
            this.condition = condition;
        }

        public override Result execute(Session session)
        {

            Result result;

            switch (type)
            {

                case StatementTypes.BEGIN_END:
                    {
                        initialiseVariables(session);

                        result = executeBlock(session);

                        break;
                    }
                case StatementTypes.LOOP:
                case StatementTypes.WHILE:
                case StatementTypes.REPEAT:
                    {
                        result = executeLoop(session);

                        break;
                    }
                case StatementTypes.IF:
                    {
                        result = executeIf(session);

                        break;
                    }
                default:
                    throw Error.runtimeError(ErrorCode.U_S0500,
                                              "StatementCompound");
            }

            if (result.isError())
            {
                result.getException().setStatementType(group, type);
            }

            return result;
        }

        private Result executeBlock(Session session)
        {

            Result result = Result.updateZeroResult;
            int i = 0;

            session.sessionContext.push();

            for (; i < statements.Length; i++)
            {
                result = statements[i].execute(session);
                result = handleCondition(session, result);

                if (result.isError())
                {
                    break;
                }

                if (result.getType() == ResultConstants.VALUE)
                {
                    break;
                }
            }

            if (result.getType() == ResultConstants.VALUE)
            {
                if (result.getErrorCode() == StatementTypes.LEAVE)
                {
                    if (result.getMainString() == null)
                    {
                        result = Result.updateZeroResult;
                    }
                    else if (label != null
                             && label.name.Equals(result.getMainString()))
                    {
                        result = Result.updateZeroResult;
                    }
                }
            }

            session.sessionContext.pop();

            return result;
        }

        private Result handleCondition(Session session, Result result)
        {

            String sqlState = null;

            if (result.isError())
            {
                sqlState = result.getSubString();
            }
            else if (session.getLastWarning() != null)
            {
                sqlState = session.getLastWarning().getSQLState();
            }
            else
            {
                return result;
            }

            if (sqlState != null)
            {
                for (int i = 0; i < handlers.Length; i++)
                {
                    StatementHandler handler = handlers[i];

                    session.clearWarnings();

                    /**
                     * @todo - if condition is "transaction rollback" promote to
                     * top call level without any further action
                     * if condition is system related promote to top level
                     * schema manipulation conditions are never handled
                     */
                    if (handler.handlesCondition(result.getSubString()))
                    {
                        session.resetSchema();

                        switch (handler.handlerType)
                        {

                            case StatementHandler.CONTINUE:
                                result = Result.updateZeroResult;
                                break;

                            case StatementHandler.UNDO:
                                session.rollbackToSavepoint();

                                result = Result.newPSMResult(StatementTypes.LEAVE,
                                                             null, null);
                                break;

                            case StatementHandler.EXIT:
                                result = Result.newPSMResult(StatementTypes.LEAVE,
                                                             null, null);
                                break;
                        }

                        Result actionResult = handler.statement.execute(session);

                        if (actionResult.isError())
                        {
                            result = actionResult;

                            handleCondition(session, result);
                        }
                        else
                        {
                            return result;
                        }
                    }
                }

                if (parent != null)
                {

                    // unhandled exception condition
                    return parent.handleCondition(session, result);
                }
            }

            return result;
        }

        private Result executeLoop(Session session)
        {

            Result result = Result.updateZeroResult;

            while (true)
            {
                if (type == StatementTypes.WHILE)
                {
                    result = condition.execute(session);

                    if (result.isError())
                    {
                        break;
                    }

                    if (!true.Equals(result.getValueObject()))
                    {
                        result = Result.updateZeroResult;

                        break;
                    }
                }

                for (int i = 0; i < statements.Length; i++)
                {
                    result = statements[i].execute(session);

                    if (result.isError())
                    {
                        break;
                    }

                    if (result.getType() == ResultConstants.VALUE)
                    {
                        break;
                    }
                }

                if (result.isError())
                {
                    break;
                }

                if (result.getType() == ResultConstants.VALUE)
                {
                    if (result.getErrorCode() == StatementTypes.ITERATE)
                    {
                        if (result.getMainString() == null)
                        {
                            continue;
                        }

                        if (label != null
                                && label.name.Equals(result.getMainString()))
                        {
                            continue;
                        }

                        break;
                    }

                    if (result.getErrorCode() == StatementTypes.LEAVE)
                    {
                        if (result.getMainString() == null)
                        {
                            result = Result.updateZeroResult;
                        }

                        if (label != null
                                && label.name.Equals(result.getMainString()))
                        {
                            result = Result.updateZeroResult;
                        }

                        break;
                    }

                    if (result.getErrorCode() == StatementTypes.RETURN)
                    {
                        break;
                    }
                }

                if (type == StatementTypes.REPEAT)
                {
                    result = condition.execute(session);

                    if (result.isError())
                    {
                        break;
                    }

                    if (true.Equals(result.getValueObject()))
                    {
                        result = Result.updateZeroResult;

                        break;
                    }
                }
            }

            return result;
        }

        private Result executeIf(Session session)
        {

            Result result = Result.updateZeroResult;
            bool execute = false;

            for (int i = 0; i < statements.Length; i++)
            {
                if (statements[i].getType() == StatementTypes.CONDITION)
                {
                    if (execute)
                    {
                        break;
                    }

                    result = statements[i].execute(session);

                    if (result.isError())
                    {
                        break;
                    }

                    Object value = result.getValueObject();

                    execute = true.Equals(value);

                    i++;
                }

                result = Result.updateZeroResult;

                if (!execute)
                {
                    continue;
                }

                result = statements[i].execute(session);

                if (result.isError())
                {
                    break;
                }

                if (result.getType() == ResultConstants.VALUE)
                {
                    break;
                }
            }

            return result;
        }

        public override void resolve()
        {

            for (int i = 0; i < statements.Length; i++)
            {
                if (statements[i].getType() == StatementTypes.LEAVE
                        || statements[i].getType() == StatementTypes.ITERATE)
                {
                    if (!findLabel((StatementSimple)statements[i]))
                    {
                        throw Error.error(
                            ErrorCode.X_42508,
                            ((StatementSimple)statements[i]).label.name);
                    }

                    continue;
                }

                if (statements[i].getType() == StatementTypes.RETURN)
                {
                    if (!root.isFunction())
                    {
                        throw Error.error(ErrorCode.X_42602, Tokens.T_RETURN);
                    }
                }
            }

            for (int i = 0; i < statements.Length; i++)
            {
                statements[i].resolve();
            }
        }

        public override void setRoot(Routine routine)
        {

            root = routine;
            /*
                    if (condition != null) {
                        condition.setRoot(routine);
                    }

                    for (int i = 0; i < statements.Length; i++) {
                        statements[i].setRoot(routine);
                    }
            */
        }

        public override String describe(Session session)
        {
            return "";
        }

        public override OrderedHashSet getReferences()
        {

            OrderedHashSet set = new OrderedHashSet();

            for (int i = 0; i < statements.Length; i++)
            {
                set.addAll(statements[i].getReferences());
            }

            for (int i = 0; i < handlers.Length; i++)
            {
                set.addAll(handlers[i].getReferences());
            }

            return set;
        }

        public void setAtomic(bool atomic)
        {
            this.isAtomic = atomic;
        }

        //
        private void setVariables()
        {

            if (variables.Length == 0)
            {
                if (parent == null)
                {
                    rangeVariables = root.getParameterRangeVariables();
                }
                else
                {
                    rangeVariables = parent.rangeVariables;
                }

                return;
            }

            HashMappedList list = new HashMappedList();

            if (parent != null)
            {
                for (int i = 0; i < parent.scopeVariables.size(); i++)
                {
                    list.add(parent.scopeVariables.getKey(i),
                             parent.scopeVariables.get(i));
                }
            }

            for (int i = 0; i < variables.Length; i++)
            {
                String name = variables[i].getName().name;
                bool added = list.add(name, variables[i]);

                if (!added)
                {
                    throw Error.error(ErrorCode.X_42606, name);
                }

                if (root.getParameterIndex(name) != -1)
                {
                    throw Error.error(ErrorCode.X_42606, name);
                }
            }

            RangeVariable range = new RangeVariable(list, true);

            rangeVariables = new RangeVariable[] {
            root.getParameterRangeVariables()[0], range
        };
            root.variableCount = list.size();
        }

        private void setHandlers()
        {

            if (handlers.Length == 0)
            {
                return;
            }

            HashSet statesSet = new HashSet();
            OrderedIntHashSet typesSet = new OrderedIntHashSet();

            for (int i = 0; i < handlers.Length; i++)
            {
                int[] types = handlers[i].getConditionTypes();

                for (int j = 0; j < types.Length; j++)
                {
                    if (!typesSet.add(types[j]))
                    {
                        throw Error.error(ErrorCode.X_42601);
                    }
                }

                String[] states = handlers[i].getConditionStates();

                for (int j = 0; j < states.Length; j++)
                {
                    if (!statesSet.add(states[j]))
                    {
                        throw Error.error(ErrorCode.X_42601);
                    }
                }
            }
        }

        private bool findLabel(StatementSimple statement)
        {

            if (label != null && statement.label.name.Equals(label.name))
            {
                if (!isLoop && statement.getType() == StatementTypes.ITERATE)
                {
                    return false;
                }

                return true;
            }

            if (parent == null)
            {
                return false;
            }

            return parent.findLabel(statement);
        }

        private void initialiseVariables(Session session)
        {

            Object[] vars = session.sessionContext.routineVariables;
            int offset = parent == null ? 0
                                             : parent.scopeVariables.size();

            for (int i = 0; i < variables.Length; i++)
            {
                try
                {
                    vars[offset + i] = variables[i].getDefaultValue(session);
                }
                catch (CoreException ) { }
            }
        }

        public override RangeVariable[] getRangeVariables()
        {
            return rangeVariables;
        }
    }
}
